package com.cr.dataprotect.manage;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.cr.dataprotect.bean.DesensitizationPropertyConfigBean;
import com.cr.dataprotect.common.MyConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 数据脱敏
 *
 * @author ChenRu1
 * @Description
 * @Date 11:36 2023/12/6
 **/
@Slf4j
@Service
public class DesensitizationManage {


    /**
     * 数据脱敏参数修改（用于脱敏后的数据进行更新操作时使用）
     *
     * @param invocation
     * @param fieldConfig
     */
    public void desensitizationParameter(Invocation invocation, Map<Class, Map<String, Object>> fieldConfig) throws Exception {
        StatementHandler statementHandler = (StatementHandler) PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

        if (sqlCommandType == SqlCommandType.UPDATE) {
            BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

            Object parameterObject = statementHandler.getParameterHandler().getParameterObject();

            Class parameterObjectClass = parameterObject.getClass();
            MapperMethod.ParamMap<Object> paramMap = null;
            // MyBatis MapperMethod.ParamMap 类型，key - value，value 可能为 PO 类型
            if (parameterObject instanceof MapperMethod.ParamMap) {
                paramMap = (MapperMethod.ParamMap<Object>) parameterObject;
            } else {
                paramMap = new MapperMethod.ParamMap<>();
                paramMap.put("", parameterObject);
            }
            for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                String paramName = entry.getKey();
                Object paramValue = entry.getValue();

                if (paramValue == null) {
                    continue;
                }
                Class<?> valueClass = paramValue.getClass();
                //paramValue 有可能为空
                if (!fieldConfig.containsKey(valueClass)) {
                    continue;
                }
                if (CollectionUtils.isNotEmpty(parameterMappings)) {
                    Map<String, Object> valMap = fieldConfig.get(valueClass);
                    List<Map<String, Object>> mapList = (List<Map<String, Object>>) valMap.get(MyConstants.PROPERTIES);
                    String[] needEncryptPropertyNameArr = new String[mapList.size()];
                    for (int i = 0; i < mapList.size(); i++) {
                        Map<String, Object> map = BeanUtil.beanToMap(mapList.get(i));
                        needEncryptPropertyNameArr[i] = map.get("name").toString();
                    }
                    List<Map<String, String>> changeList = new ArrayList<>();
                    List<ParameterMapping> removeParameterMapping = new ArrayList<>();
                    for (ParameterMapping parameterMapping : parameterMappings) {
                        if (parameterMapping.getMode() != ParameterMode.OUT) {
                            String propertyName = parameterMapping.getProperty();

                            String trimmedParamName = propertyName;
                            if (StringUtils.isNotEmpty(paramName) && trimmedParamName.startsWith(paramName + ".")) {
                                trimmedParamName = trimmedParamName.substring(paramName.length() + 1);
                                log.debug("origin param name: {}, trimmed param name:{}", propertyName, trimmedParamName);
                            }
                            if (ArrayUtils.contains(needEncryptPropertyNameArr, trimmedParamName)) {
                                DesensitizationPropertyConfigBean propertyConfigBean = getPropertyConfig(mapList).get(trimmedParamName);
                                //如果参数是敏感字段，则把value取出来加密
                                Object value;
                                if (boundSql.hasAdditionalParameter(propertyName)) {
                                    value = boundSql.getAdditionalParameter(propertyName);
                                } else if (mappedStatement.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObjectClass)) {
                                    value = paramValue;
                                } else {
                                    Configuration configuration = mappedStatement.getConfiguration();
                                    MetaObject newMetaObject = configuration.newMetaObject(paramValue);
                                    value = newMetaObject.getValue(trimmedParamName);
                                    Field additionalParametersField = boundSql.getClass().getDeclaredField("additionalParameters");
                                    additionalParametersField.setAccessible(true);
                                    Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
                                    if (value == null && MapUtils.isNotEmpty(additionalParameters)) {
                                        value = additionalParameters.get(propertyName);
                                    }
                                }
                                if (Objects.nonNull(value)) {
                                    String valueStr = String.valueOf(value);

                                    String sensitiveData = getSensitiveData(valueStr, propertyConfigBean);
                                    if (valueStr.equals(sensitiveData)) {
                                        Map<String, String> map = new HashMap<>();
                                        String name = StrUtil.toUnderlineCase(trimmedParamName).toUpperCase();
                                        map.put("[^_a-zA-Z]" + name + "=[?]", name + "=" + name);
                                        map.put("[^_a-zA-Z]" + name + "\\s*=\\s*[?]", name + " = " + name);
                                        changeList.add(map);
                                        removeParameterMapping.add(parameterMapping);
                                    }
                                }
                            }
                        }
                    }
                    if (CollectionUtils.isNotEmpty(changeList)) {
                        String sql = boundSql.getSql();
                        for (Map<String, String> map : changeList) {
                            for (String key : map.keySet()) {
                                sql = sql.replaceAll(key, map.get(key));
                            }
                        }

                        metaObject.setValue("delegate.boundSql.sql", sql);

                        parameterMappings.removeAll(removeParameterMapping);
                        metaObject.setValue("delegate.boundSql.parameterMappings", parameterMappings);
                    }
                }

            }
        }
    }

    /**
     * 数据脱敏
     *
     * @param result
     * @param fieldConfig
     * @return
     */
    public Object desensitizationData(Object result, Map<Class, Map<String, Object>> fieldConfig) throws Throwable {
        if (result instanceof Collection) {
            Collection resultList = (Collection) result;
            if (CollectionUtils.isNotEmpty(resultList)) {
                for (Object o : resultList) {
                    if (null == o) {
                        continue;
                    }
                    //判断是否属于配置的加密类
                    if (!fieldConfig.containsKey(o.getClass())) {
                        //返回结果不是sensitive中配置的类型
                        break;
                    }
                    Map<String, Object> valMap = fieldConfig.get(o.getClass());
                    List<Map<String, Object>> mapList = (List<Map<String, Object>>) valMap.get(MyConstants.PROPERTIES);
                    this.process(o, mapList);
                }
            }
        } else {
            if (null != result && fieldConfig.containsKey(result.getClass())) {
                Map<String, Object> valMap = fieldConfig.get(result.getClass());
                List<Map<String, Object>> mapList = (List<Map<String, Object>>) valMap.get(MyConstants.PROPERTIES);
                this.process(result, mapList);
            }
        }
        return result;
    }


    /**
     * 对返回值对象的具体处理过程.
     *
     * @param resultObj 返回值对象
     * @throws IllegalAccessException 反射可能抛出的异常
     */
    private void process(Object resultObj, List<Map<String, Object>> mapList) throws IllegalAccessException {
        if (resultObj == null) {
            return;
        }
        decryptObject(resultObj, mapList);
    }

    /**
     * 解密字段
     *
     * @param resultObj
     * @throws IllegalAccessException
     */
    private void decryptObject(Object resultObj, List<Map<String, Object>> mapList) throws IllegalAccessException {
        //保存已处理过的对象，避免循环处理
        Set<Object> handlingClasses = SetUtils.newIdentityHashSet();
        decryptAllFields(resultObj, handlingClasses, mapList);
    }

    private void decryptAllFields(Object resultObj, Set<Object> handlingClasses, List<Map<String, Object>> mapList)
            throws IllegalAccessException {
        List<Field> declaredFields = new ArrayList<>();
        //直接反射取得field有stack overflow问题
        ReflectionUtils.doWithFields(resultObj.getClass(), new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                declaredFields.add(field);
            }
        });


        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            Object value = declaredField.get(resultObj);
            if (value instanceof String) {
                decryptField(resultObj, declaredField, (String) value, mapList);
            } else if (value instanceof Character) {
                decryptField(resultObj, declaredField, ((Character) value).toString(), mapList);
            } else if (value instanceof Object && value.getClass().isArray()) {
                int length = Array.getLength(value);
                if (length > 0) {
                    if (value.getClass().getComponentType() != null && value.getClass().getComponentType().toString().equals("char")) {
                        String valueToStr = new String((char[]) value);
                        decryptField(resultObj,
                                declaredField, valueToStr, mapList);
                    } else {
                        for (int idx = 0; idx < length; idx++) {
                            Object object = Array.get(value, idx);
                            if (canDecryptedType(object)) {
                                decryptAllFields(object, handlingClasses, mapList);
                            }
                        }
                    }
                }
            } else if (value instanceof Object && value instanceof Collection) {
                Collection objects = (Collection) value;
                Iterator iterator = objects.iterator();
                while (iterator.hasNext()) {
                    decryptAllFields(iterator.next(), handlingClasses, mapList);
                }
            } else if (value instanceof Object &&
                    canDecryptedType(value)) {
                if (handlingClasses.contains(resultObj)) {
                    return;
                }
                handlingClasses.add(resultObj);
                decryptAllFields(value, handlingClasses, mapList);
            }
        }
    }

    private static boolean canDecryptedType(Object value) {
        return value != null && !(value.getClass().isPrimitive() || value.getClass().isEnum() || value instanceof Number
                || value instanceof Boolean || value instanceof Date || value instanceof Time || value instanceof Timestamp);
    }

    private void decryptField(Object resultObj, Field declaredField, String value, List<Map<String, Object>> mapList) throws IllegalAccessException {
        String[] attributeNameArr = new String[mapList.size()];
        for (int i = 0; i < mapList.size(); i++) {
            Map<String, Object> map = BeanUtil.beanToMap(mapList.get(i));
            attributeNameArr[i] = map.get("name").toString();

        }
        //判断该属性名是否要加密
        if (!ArrayUtils.contains(attributeNameArr, declaredField.getName())) {
            return;
        }
        DesensitizationPropertyConfigBean propertyConfigBean = getPropertyConfig(mapList).get(declaredField.getName());
        value = getSensitiveData(value, propertyConfigBean);

        if (declaredField.getType().toString().equals("class [C")) {
            char[] valueOfChar = value.toCharArray();
            declaredField.set(resultObj, valueOfChar);
        } else {
            declaredField.set(resultObj, value);
        }
    }

    /**
     * 将配置文件中的配置转换 k：name value:xx
     *
     * @param mapList
     * @return
     */
    private Map<String, DesensitizationPropertyConfigBean> getPropertyConfig(List<Map<String, Object>> mapList) {
        List<DesensitizationPropertyConfigBean> propertyConfigBeanList = new ArrayList<>();
        for (int i = 0; i < mapList.size(); i++) {
            Map<String, Object> map = BeanUtil.beanToMap(mapList.get(i));
            DesensitizationPropertyConfigBean propertyConfigBean = BeanUtil.mapToBean(map, DesensitizationPropertyConfigBean.class, true);
            ;
            propertyConfigBeanList.add(propertyConfigBean);
        }

        return propertyConfigBeanList.stream().collect(Collectors.toMap(DesensitizationPropertyConfigBean::getName, it -> it));
    }

    /**
     * 通过配置获取脱敏后的数据
     *
     * @param data               原数据
     * @param propertyConfigBean 配置
     * @return
     */
    public static String getSensitiveData(String data, DesensitizationPropertyConfigBean propertyConfigBean) {
        int begin = 0, end = 0;
        if (ObjectUtils.isNotNull(propertyConfigBean.getBegin()) && ObjectUtils.isNotNull(propertyConfigBean.getEnd())) {
            if (propertyConfigBean.getBegin() > propertyConfigBean.getEnd()) {
                throw new RuntimeException("配置脱敏长度异常，请检查配置");
            }

            begin = propertyConfigBean.getBegin();
            end = propertyConfigBean.getEnd();
        } else if (ObjectUtils.isNull(propertyConfigBean.getBegin()) && ObjectUtils.isNotNull(propertyConfigBean.getEnd())) {
            end = propertyConfigBean.getEnd();
        } else if (ObjectUtils.isNotNull(propertyConfigBean.getBegin()) && ObjectUtils.isNull(propertyConfigBean.getEnd())) {
            begin = propertyConfigBean.getBegin();
            end = data.length();
        } else if (ObjectUtils.isNull(propertyConfigBean.getBegin()) && ObjectUtils.isNull(propertyConfigBean.getEnd())) {
            end = data.length();
        }

        char val = '*';
        if (!(CharUtils.compare(propertyConfigBean.getValue(), '\u0000') <= 0)) {
            val = propertyConfigBean.getValue();
        }
        return StrUtil.replace(data, begin, end + 1, val);
    }

}
